/*
Copyright (c) 2013 Mihail Volkov

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "../server/htmd_winsocklocal.h"
// ^have to include it first because of defect in M$V$ SDK
#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include "eventsengine.h"

#include "sample_units.h"

#define FRAME_DURATION 20 // millisec

// keypress event
struct EvtKeyPress: public Event<EvtKeyPress>
{
	int chrCode;
	EvtKeyPress (int theChrCode): chrCode (theChrCode) {}
};

#ifndef DISABLE_HTMD
struct EvtDoFileBrowser: public Event<EvtDoFileBrowser> {};
void hatulDoFileBrowser ();

// debug/test part of update loop
void hatulUpdate ()
{
	bool wasConnected = HtMdSupervisor::isConnected ();
	HtMdSupervisor::update ();
	bool isConnected = HtMdSupervisor::isConnected ();
	if (isConnected!=wasConnected)
	{
		if (isConnected) printf ("HM: client has connected\n");
		else printf ("HM: client has disconnected\n");
	}

	static bool inFileBrowser = false;
	if (!inFileBrowser)
	{
		EvtDoFileBrowser *pEDFB = EvtDoFileBrowser::poll ();
		if (pEDFB!=NULL)
		{
			inFileBrowser = true;
			hatulDoFileBrowser ();
			inFileBrowser = false;
		}
	}
}

// file browser
void engineUpdate ();
void hatulDoFileBrowser ()
{
	HtMdTestFileBrowser fb (".");
	HTMD_PRINTF (HATUL_SERVER,
		("Entered test file browser, app main loop suspended"));
	printf ("HM: Suspend for test file browser...\n"); // print in app
	while (!fb.isFinished ())
	{
		engineUpdate ();
		EvtKeyPress *pEKP = EvtKeyPress::poll ();
		if (pEKP!=NULL && pEKP->chrCode==27)
			break; // abort by escape
		Sleep (FRAME_DURATION);
	}
	HTMD_PRINTF (HATUL_SERVER,
	("Exitted test file browser, app main loop resumed"));
	printf ("HM: File browser done, resuming...\n"); // print in app
}
#endif // DISABLE_HTMD

void engineUpdate ()
{
	// track keypresses
	if (kbhit ())
		EventsTracker::post (new EvtKeyPress (getch ()));

	// update app events
	EventsTracker::update ();

#ifndef DISABLE_HTMD
	// Hatul Madan part of update
	hatulUpdate ();
#endif
}

// application states:

// intro
enum StateIntroResult
{
	SI_OK,
	SI_EXIT
};

StateIntroResult performIntro ()
{
	HTMD_PRINTF (GAME,("Entering intro state"));
	int msSkipped;

	printf ("NEXT GENERATION GAME!!!\n");
	HTMD_PRINTF (GAME,("Intro: displaying intro frame 1"));
	for (msSkipped=0; msSkipped<5000; msSkipped+=FRAME_DURATION)
	{
		engineUpdate ();
		EvtKeyPress *pEKP = EvtKeyPress::poll ();
		if (pEKP!=NULL)
		{
			if (pEKP->chrCode==27) goto EXIT_ISSUED;
			// not escape - abort frame
			HTMD_PRINTF (GAME,("Intro: user aborted intro frame 1 via any key"));
			break;
		}
		Sleep (FRAME_DURATION);
	}

	printf ("FEARLESS VAMPIRE SLAYER\n");
	HTMD_PRINTF (GAME,("Intro: displaying intro frame 2"));
	for (msSkipped=0; msSkipped<5000; msSkipped+=FRAME_DURATION)
	{
		engineUpdate ();
		EvtKeyPress *pEKP = EvtKeyPress::poll ();
		if (pEKP!=NULL)
		{
			if (pEKP->chrCode==27) goto EXIT_ISSUED;
			// not escape - abort frame
			HTMD_PRINTF (GAME,("Intro: user aborted intro frame 2 via any key"));
			break;
		}
		Sleep (FRAME_DURATION);
	}

	HTMD_PRINTF (GAME,("Intro state completed"));
	return SI_OK;

EXIT_ISSUED:
	HTMD_PRINTF (GAME,("Intro: user issued app exit command"));
	return SI_EXIT;
}

// main menu
enum StateMainMenuResult
{
	SMM_NEW_GAME,
	SMM_EXIT
};

StateMainMenuResult performMainMenu ()
{
	HTMD_PRINTF (GAME,("Entering main menu state"));
	printf ("Main menu\n1 - New game, 2 - Exit\n");

	for (;;)
	{
		engineUpdate ();
		EvtKeyPress *pEKP = EvtKeyPress::poll ();
		if (pEKP!=NULL)
		{
			if (pEKP->chrCode==27) goto EXIT_ISSUED;
			if (pEKP->chrCode=='1')
			{
				HTMD_PRINTF (GAME,("Main menu: user has chosen new game"));
				return SMM_NEW_GAME;
			}
			if (pEKP->chrCode=='2')
			{
				HTMD_PRINTF (GAME,("Main menu: user has chosen exit"));
				return SMM_EXIT;
			}
		}
	}

EXIT_ISSUED:
	HTMD_PRINTF (GAME,("Menu: user issued app exit command"));
	return SMM_EXIT;
}

// game

#ifndef DISABLE_HTMD
struct EvtTestUpdate: public Event<EvtTestUpdate> {};

class IntTester
{
	std::string mName;
	int &mInt;
public:
	IntTester (const std::string &name,int &theInt):
		mName (name), mInt (theInt) {}
	void change (const std::string &param)
	{
		int data = 0;
		if (sscanf (param.c_str (),"%d",&data)>=0)
		{
			mInt = data;
			HTMD_PRINTF (TEST,("%s set to %d",
				mName.c_str (),mInt));
			EventsTracker::post (new EvtTestUpdate ());
		}
	}
	void check (const std::string &)
	{
		HTMD_PRINTF (TEST,("%s == %d",mName.c_str (),mInt));
	}
};
#endif

enum StateGameResult
{
	SG_WIN,
	SG_EXIT
};

StateGameResult performNewGame ()
{
	HTMD_PRINTF (GAME,("Entering main menu state"));

	int msSkipped;
	const char *dialogs[] =
	{
		"Greetings, Stranger!",
		"Our town is in trouble...",
		"Recently vampires started to pop up from under the ground!",
		"There are just 50 of them, but we townsfolk are cowardly idiots!",
		"Please help us good mr. Stranger!",
		NULL
	};

	for (int diaNum=0; dialogs[diaNum]!=NULL; diaNum++)
	{
		HTMD_PRINTF (GAME,("Starting dialog #%d",diaNum));
		printf ("%s\n",dialogs[diaNum]);
		for (msSkipped=0; msSkipped<3000; msSkipped+=FRAME_DURATION)
		{
			engineUpdate ();
			EvtKeyPress *pEKP = EvtKeyPress::poll ();
			if (pEKP!=NULL)
			{
				if (pEKP->chrCode==27) goto EXIT_ISSUED;
				// not escape - abort frame
				HTMD_PRINTF (GAME,("Game: user skipped a dialog via any key"));
				break;
			}
			Sleep (FRAME_DURATION);
		}
	}

	HTMD_PRINTF (GAME,("Starting pre-game hint"));
	printf ("HINT: press SPACEBAR to slay a vampire!\nWait 3 sec!\n");
	for (msSkipped=0; msSkipped<3000; msSkipped+=FRAME_DURATION)
	{
		engineUpdate ();
		EvtKeyPress *pEKP = EvtKeyPress::poll ();
		if (pEKP!=NULL)
		{
			if (pEKP->chrCode==27) goto EXIT_ISSUED;
			// not escape - abort frame
			HTMD_PRINTF (GAME,("Game: user skipped hint via any key"));
			break;
		}
		Sleep (FRAME_DURATION);
	}

	{
		HTMD_PRINTF (GAME,("Gameplay started"));

		int vampiresLeft = 50;
#ifndef DISABLE_HTMD
		IntTester intTester ("Vampires Left",vampiresLeft);
		HtMdTestConsole::CommandHandler tcCmdSetVampires (
			"setvampires",&intTester,&IntTester::change);
		tcCmdSetVampires.setHelp ("setvampires N - set number of vampires left to N");
		HtMdTestConsole::CommandHandler tcCmdGetVampires (
			"getvampires",&intTester,&IntTester::check);
		tcCmdGetVampires.setHelp ("getvampires - print number of remaining vampires");

		HTMD_PRINTF (TEST,("In game you have 2 extra test commands: setvampires and getvampires.\n"
		"Check ? for more info."));
#endif

		for (;;)
		{
#ifndef DISABLE_HTMD
		GAME_LOOP_RE_ITERATE:
#endif
			printf ("Vampires left: %d Vampires slain: %d\n",vampiresLeft,50-vampiresLeft);
			for (;;)
			{
				engineUpdate ();
				EvtKeyPress *pEKP = EvtKeyPress::poll ();
				if (pEKP!=NULL)
				{
					if (pEKP->chrCode==27) goto EXIT_ISSUED;
					if (pEKP->chrCode==32) goto VAMPIRE_DOWN;
				}
#ifndef DISABLE_HTMD
				EvtTestUpdate *pETU = EvtTestUpdate::poll ();
				if (pETU!=NULL) goto GAME_LOOP_RE_ITERATE;
#endif
				Sleep (FRAME_DURATION);
			}
		VAMPIRE_DOWN:;
			if (vampiresLeft<=0) break;
			else vampiresLeft--;
		}

		HTMD_PRINTF (GAME,("Gameplay finished"));
	}

	printf ("Greatest thanks to you!\nYou have saved our town from these cuntabhorrent creatures!\n");
	for (;;)
	{
		engineUpdate ();
		EvtKeyPress *pEKP = EvtKeyPress::poll ();
		if (pEKP!=NULL)
		{
			if (pEKP->chrCode==27) goto EXIT_ISSUED;
			if (pEKP->chrCode==32) break;
		}
		Sleep (FRAME_DURATION);
	}

	return SG_WIN;

EXIT_ISSUED:
	HTMD_PRINTF (GAME,("Game: user issued game exit command"));
	return SG_EXIT;
}

#ifndef DISABLE_HTMD
// handler of 'echo' global command
void htmdEchoCommandHandler (const std::string &params)
{
	HTMD_PRINTF (SYSTEM,("Echo - %s",params.c_str ()));
}

// handler of 'files' global command
void htmdFilesCommandHandler (const std::string &)
{
	EventsTracker::post (new EvtDoFileBrowser ());
}
#endif

int main (int argc,char *argv[])
{
	(void)argc;
	(void)argv;
#ifndef DISABLE_HTMD
	std::string port = "1234";
	if (argc>1) port = argv[1];
	HtMdConnection_WinsockLocal wsc (port);
	if (!wsc.isOpenOk ())
	{
		printf ("Failed to open server on 127.0.0.1:%s\n",port.c_str ());
		if (wsc.getWinsockError ()>0)
			printf ("Winsock error: %d\nIf it's 10048 (address/port busy), try other port by running\nsample_app.exe portnumber",
			wsc.getWinsockError ());
		return 1;
	}
	HtMdSupervisor wssv (wsc);
	HtMdTestConsole testConsole;

	HtMdTestConsole::CommandHandler tcCmdEcho (
		"echo",htmdEchoCommandHandler);
	// ^in C++11, instead of htmdEchoCommandHandler, a lambda
	// with void (const std::string &) signature can be used
	tcCmdEcho.setHelp
		("echo params - prints params back into SYSTEM stream");

	HtMdTestConsole::CommandHandler tcCmdFiles (
		"files",htmdFilesCommandHandler);
	tcCmdFiles.setHelp
		("files - invoke test file browser");
#endif // DISABLE_HTMD

	EventsTracker eventsTracker;

#ifndef DISABLE_HTMD
	printf ("HM: Hatul Madan enabled. Connect helper info: %s\n",
		wsc.getConnectionUserPrompt ().c_str ());
	printf ("Connect with HM client or press any key to start the application...\n");
	for (;;)
	{
		engineUpdate ();
		if (EvtKeyPress::poll ()!=NULL) break; // any key
		//if (HtMdSupervisor::isConnected ()) break; // client connected
		if (testConsole.isClientReady ()) break; // client connected and ready
		Sleep (FRAME_DURATION);
	}
#endif // DISABLE_HTMD

	HTMD_PRINTF (NOTICE,("Entering application main loop"));
//STATE_INTRO:
	{
		StateIntroResult introResult = performIntro ();
		switch (introResult)
		{
		case SI_OK: goto STATE_MAIN_MENU;
		case SI_EXIT: goto STATE_FINISH;
		default:;
		}
	}

STATE_MAIN_MENU:
	{
		StateMainMenuResult mainMenuResult = performMainMenu ();
		switch (mainMenuResult)
		{
		case SMM_NEW_GAME: goto STATE_NEW_GAME;
		case SMM_EXIT: goto STATE_FINISH;
		default:;
		}
	}

STATE_NEW_GAME:
	{
		StateGameResult gameResult = performNewGame ();
		switch (gameResult)
		{
		case SG_WIN: goto STATE_MAIN_MENU;
		case SG_EXIT: goto STATE_FINISH;
		default:;
		}
	}

STATE_FINISH:
	HTMD_PRINTF (NOTICE,("Application main loop completed"));

#ifndef DISABLE_HTMD
	// a finalization loop to let HTMD output be flushed to the client
	printf ("HM: Program shutdown - disconnect HM client or press any key...\n");
	for (;;)
	{
		engineUpdate ();
		if (EvtKeyPress::poll ()!=NULL) break; // any key
		if (!HtMdSupervisor::isConnected ()) break; // client disconnected
		Sleep (FRAME_DURATION);
	}
#endif // DISABLE_HTMD

	return 0;
}
